/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package FMMLogic;

import be.ac.ulg.montefiore.run.jahmm.Hmm;
import be.ac.ulg.montefiore.run.jahmm.ObservationDiscrete;
import Domain.Output;
import Domain.Cluster;
import be.ac.ulg.montefiore.run.jahmm.OpdfDiscrete;
import be.ac.ulg.montefiore.run.jahmm.toolbox.KullbackLeiblerDistanceCalculator;
import java.util.ArrayList;
import java.util.List;
import java.io.FileOutputStream;
import Adapter.DomainAdapter;
import DataAccess.HmmWriter;
import java.io.PrintStream;

/**
 *
 * @author Allen
 */


public class LFMMCluster {

    private int k;
    private double delta_0 = 0.00001;
    private boolean DEBUG = false;
    private int max_round = 30;

    public LFMMCluster(int k) {
        this.k = k;
    }

    public LFMMCluster(int k, double delta_0){
        this.k = k;
        this.delta_0 = delta_0;
    }

    public LFMMCluster(int k, double delta_0, boolean DEBUG, int max_round){
        this.k = k;
        this.delta_0 = delta_0;
        this.DEBUG = DEBUG;
        this.max_round = max_round;
    }
    public void push_to_nearest(ArrayList<Cluster> clusters,
            Hmm<ObservationDiscrete<Output>> hmm) {
        double distance = Double.MAX_VALUE;
        int near_k = 0;
        for (int j = 0; j < k; j++) {
            double d = clusters.get(j).distance(hmm);
            if (distance > d) {
                distance = d;
                near_k = j;
            }
        }
        clusters.get(near_k).add(hmm);
    }

    public ArrayList<Cluster> kmeansCalculate(List<Hmm<ObservationDiscrete<Output>>> h_list) {

        ArrayList<Hmm<ObservationDiscrete<Output>>> old_avgs =
                new ArrayList<Hmm<ObservationDiscrete<Output>>>();
        ArrayList<Hmm<ObservationDiscrete<Output>>> new_avgs =
                new ArrayList<Hmm<ObservationDiscrete<Output>>>();
        ArrayList<Cluster> cluster_list = new ArrayList<Cluster>();

        double init_T[][] = new double[3][3];
        double init_O[][] = new double[3][3];
        /*choose size/1, size/2 ... size/k as the init cluster*/
        for (int i = 0; i < k; i++) {
            old_avgs.add(DomainAdapter.toHmm(init_T, init_O));
            new_avgs.add(DomainAdapter.cloneHmm(h_list.get((h_list.size() - 1) / (i + 1))));
        }

        double delta = Double.MAX_VALUE;
        int round = 0;
        ArrayList<Cluster> clusters = new ArrayList<Cluster>();
        for (int i = 0; i < k; i++) {
            clusters.add(new Cluster(new_avgs.get(i)));
        }
        
        while (true) {
            round ++;
            if (delta <= delta_0 || round >= max_round) {
                cluster_list = clusters;
                break;
            }
            
            clusters = new ArrayList<Cluster>();
            for (int i = 0; i < k; i++) {
                clusters.add(new Cluster(new_avgs.get(i)));
            }


            for (int i = 0; i < h_list.size(); i++) {
                push_to_nearest(clusters, h_list.get(i));
            }
            
            old_avgs.clear();
            for (int i = 0; i < k; i++) {
                try {
                    old_avgs.add(
                            (Hmm<ObservationDiscrete<Output>>) new_avgs.get(i).clone());
                } catch (Exception e) {
                }
            }
            new_avgs = new ArrayList<Hmm<ObservationDiscrete<Output>>>();

            delta = 0;
            if(!DEBUG){
                for (int i = 0; i < k; i++) {
                    new_avgs.add(DomainAdapter.cloneHmm(clusters.get(i).getAvgHmm()));
                    delta += Cluster.distanceBetween(old_avgs.get(i), new_avgs.get(i));
                }
            } else {
                try {
                    String filename = "data_test/cluster/cluster" + round + ".txt";
                    FileOutputStream fs = new FileOutputStream(filename);
                    PrintStream ps = new PrintStream(fs);
                    System.setOut(ps);
                    for (int i = 0; i < k; i++) {
                        new_avgs.add(DomainAdapter.cloneHmm(clusters.get(i).getAvgHmm()));
                        delta += Cluster.distanceBetween(old_avgs.get(i), new_avgs.get(i));
                        java.text.DecimalFormat df = new java.text.DecimalFormat("#0.00000");
                        System.out.println("Cluster" + i);
                        double T[][] = new double[3][3];
                        double O[][] = new double[3][3];

                        DomainAdapter.toMatrix(old_avgs.get(i), T, O);
                        HmmWriter.writeMatrix(ps, T);
                        ps.println();
                        HmmWriter.writeMatrix(ps, O);
                        ps.println();
                        DomainAdapter.toMatrix(new_avgs.get(i), T, O);
                        HmmWriter.writeMatrix(ps, T);
                        ps.println();
                        HmmWriter.writeMatrix(ps, O);
                        ps.println();

                        System.out.println("old avg distance: " + df.format(clusters.get(i).avg_distance_to(old_avgs.get(i))));
                        System.out.println("new avg distance: " + df.format(clusters.get(i).avg_distance_to(new_avgs.get(i))));
                        for (int j = 0; j < h_list.size(); j++) {
                            if (j < 10) {
                                System.out.print(" ");
                            }
                            System.out.print("hmm" + j + "to old:");
                            System.out.print(df.format(Cluster.distanceBetween(old_avgs.get(i), h_list.get(j))));
                            System.out.print("to new:");
                            System.out.println(df.format(Cluster.distanceBetween(new_avgs.get(i), h_list.get(j))));
                        }
                    }
                    System.out.println("delta:" + delta);
                    ps.close();
                    fs.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }/* end of while(true) */
        if(true) {
            String filename = "data_test/cluster/cluster_result.txt";
            try {
                FileOutputStream fs = new FileOutputStream(filename);
                PrintStream ps = new PrintStream(fs);

                for(int i = 0; i < cluster_list.size(); i ++){
                    cluster_list.get(i).printCluster(ps);
                }
                
                fs.close();
                ps.close();
            } catch (Exception e){}
        }
        return cluster_list;
    }

}
